<?php
namespace pfp\lib {
    \defined('_JEXEC') or die('Restricted access');

    /**
     * Copyright (c) 2012, George Zeakis<zeageorge@google.com>
     * This program is free software: you can redistribute it and/or modify
     *   it under the terms of the GNU General Public License as published by
     *   the Free Software Foundation, either version 3 of the License, or
     *   any later version.
     *
     *    This program is distributed in the hope that it will be useful,
     *   but WITHOUT ANY WARRANTY; without even the implied warranty of
     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     *   GNU General Public License for more details.
     *
     *   You should have received a copy of the GNU General Public License
     *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
     */

    /**
     * Description of deepDynamicContainer
     *     With this class we can have a Tree structure like, dynamicaly created arrays, working like objects,
     *  we can assign properties, values, objects, etc. in dev time or runtime, dynamicaly
     *  and we can always get back these properties, values, objects, etc. as they were or as an assoc array.
     * 
     * -----------------------------------------------------------------------------
     *  example:
     *  $ddc_test1 = new deepDynamicContainer(NULL, "Root", "Root value");
     *           
     *           $ddc_test1->c1 = "child1";
     *           $ddc_test1->c2 = "child2";
     *           $ddc_test1->c3 = "child3";
     *           $ddc_test1->c4 = "child4";
     *           $ddc_test1->c5 = "child5";
     * 
     * $children = array(
     *               'type'=>array('test'),
     *               'dddddd'=>'fffffff',
     *               'just an integer array indexed,      text value',
     *               array(), // empty array
     *               'deep'=>$ddc_test1,
     *           );
     *   $ddc = new deepDynamicContainer(NULL, "Root", NULL, $children);
     *   $ddc->name->mid = "Zeakis";
     * 
     *   // we can also use the simplest arrays, ones with integer indexes, like:
     *   $ttttt = "1";
     *   echo $ddc->$ttttt("name")."<br />";
     *   echo $ddc->$ttttt()."<br />";
     *   var_dump($ddc->$ttttt("get_children"));
     * 
     *   echo $ddc->name()." ".$ddc->name->mid();
     *   echo $ddc->name->first()." ".$ddc->name->last();
     *   $ddc_children_objects = $ddc("get_children");
     *   $ddc_name = $ddc("get_name");
     *   $ddc_value = $ddc();
     *   $ddc_children_asArray = $ddc("get_children2Array");
     * 
     *   $ddc_name_name = $ddc->name("get_name");
     *   $ddc_name_value = $ddc->name();
     *   $ddc_name_parent_object = $ddc->name("get_parent");
     *   $ddc_name_children_objects = $ddc->name("get_children");
     *   $ddc_name_children_asArray = $ddc->name("get_children2Array");
     *   
     *   
     * -----------------------------------------------------------------------------
     * 
     * @copyright Copyright (c) 2012, George Zeakis<zeageorge@google.com>
     * @license http://www.gnu.org/licenses/gpl-3.0.txt GNU GENERAL PUBLIC LICENSE
     * 
     * @author George Zeakis<zeageorge@google.com>
     */
    class deepDynamicContainer {
        protected $_children = array(); // of objects of type self [deepDynamicContainer]
        protected $_parent = \NULL;
        protected $_name   = \NULL;
        protected $_value  = \NULL;

        /**
         * 
         * @param pfp\lib\deepDynamicContainer $parent
         * @param string $name
         * @param mix $value
         * @param array $children
         * @return pfp\lib\deepDynamicContainer 
         */
        public function __construct($parent=\NULL, $name=\NULL, $value=\NULL, array $children=array()) {
            $this->_parent = $parent;
            $this->_name = (string)$name;
            $this->_value = $value;
            if (\count($children)>0){ $this->_children = $this->array2objects($children); }
        }

        /**
         * Description:
         *   You can access the numbered (integer) keys of the array, like the way below:   
         *       $ttttt = "0"; // here we assign an integer to a var
         *       echo $ddc->$ttttt("get_name")."<br />"; // next, we use this var as function/property name, 
         *                                           // and guess what, it WORKS !!!!
         *       echo $ddc->$ttttt("get_name"); 
         *       var_dump($ddc->$ttttt("get_children"));
         * 
         * @param array $array The array to be converted to an assoc array of self [deepDynamicContainer] objects
         * @return array 
         */
        private function array2objects(array $array){
            $array2return = array();
            foreach ($array as $key => $value) {
                if (\is_array($value)){
                    if (\count($value)<1){
                        $array2return[$key] = new self($this, $key, $value);
                    } else {
                        $array2return[$key] = $this->array2objects($value);
                    }    
                } else {
                    if ($value instanceof self){
                        $old_children = $value("get_children");
                        $old_val = $value();
                        $array2return[$key] = new self($this, $key, $old_val, $old_children);
                    } else {
                        $array2return[$key] = new self($this, $key, $value);
                    }
                }
            }
            return $array2return;
        }

        public function __get($name){ // get/return  children object
            if (!isset($this->_children[$name])){ $this->_children[$name] = new self($this, $name); }
            return $this->_children[$name];
        }

        public function __set($name,$value){
            if ($value instanceof self){
                $old_children = $value("get_children");
                $old_val = $value();
                $this->_children[$name] = new self($this, $name, $old_val, $old_children);
            } else {
                $this->_children[$name] = new self($this, $name, $value);
            }  
        }

        public function __call($name, $arguments) {
            if (!isset($this->_children[$name])){ throw new \Exception("child node ($name) does not exists."); }
            return isset($arguments[0]) ? $this->_children[$name]($arguments[0]) : $this->_children[$name]();
        }

        public function __invoke($what="value") {
            switch ($what) {
                case "get_name":
                    return $this->getName();
                    break;
                case "get_parent":
                    return $this->getParent();
                    break;
                case "get_children":
                    return $this->getChildren();
                    break;
                case "get_children2Array":
                    return $this->getChildren2Array();
                    break;
                default: // returns [my value] by default !
                    return $this->getValue();
                    break;
            }
        }

        /**
         * 
         * @return mix Returns the $this->_value, could be anything.
         */
        private function getValue(){ return $this->_value; }

        /**
         * 
         * @return string Returns my name!
         */
        private function getName() { return (string)$this->_name; }

        /**
         * 
         * @return object|NULL Returns an object of type "self" (deepDynamicContainer) or NULL if we are the "Root" object
         */
        private function getParent(){ return $this->_parent; }

        /**
         * 
         * @return array Returns an array of objects, of type "self" (deepDynamicContainer)
         */
        private function getChildren(){ return $this->_children; }

        /**
         * @return array Returns an assoc array of children with key-values = to childrens' name-value
         *
         */
        private function getChildren2Array(){ return $this->objects2array($this->_children); }

        /**
         * 
         * @param array $arrayOfobjects
         * @return array Returns an assoc array of children with key-values = to childrens' name-value
         *                  structure is: 
         *                      $children = array(
         *                           'name'=>array('value'=>'George',
         *                               'children'=>array(
         *                                   'first'=>array('value'=>'John',
         *                                       'children'=>array(),
         *                                   ),
         *                                   'last'=>array('value'=>'Smith',
         *                                       'children'=>array(),
         *                                   ),
         *                               ),
         *                           ),
         *                           array('value'=>'zero object value',
         *                               'children'=>array(),
         *                           ),
         *                       );
         */
        private function objects2array(array $arrayOfobjects){
            $array2return = array();
            foreach ($arrayOfobjects as $key => $value) {
                $array2return[$key] = array('value'=>$value(), 'children'=>$value('get_children2Array'));
            }
            return $array2return;
        }

        public function __isset($name){
            if (isset($this->_children[$name]) || \array_key_exists($name, $this->_children)){ return \TRUE;}
            return \FALSE;
        }

        public function __unset($name){
            if (isset($this->_children[$name]) || \array_key_exists($name, $this->_children)){
                unset($this->_children[$name]);
            }
        }

        public function __destruct() { }
    }
}
?>
